Создание модели
===============

Прежде чем создать HTML код формы, нам необходимо определить, какие данные мы
будем получать от пользователей и каким правилам они должны соответствовать.
Для фиксации этой информации можно использовать класс модели данных.
Модель данных, как говорится в разделе [Модель](/doc/guide/basics.model),
это центральное место для хранения и проверки данных, вводимых пользователем.

В зависимости от того, каким образом используются введённые данные,
мы можем использовать два типа моделей. Если мы получаем данные, обрабатываем их,
а затем удаляем, то используем [модель формы](/doc/guide/basics.model); если же
после получения и обработки данных мы сохраняем их в базе данных, то используем [Active Record](/doc/guide/database.ar).
Оба типа моделей данных используют один и тот же базовый класс [CModel], который определяет
общий интерфейс, используемый формами.

> Note|Примечание: В примерах этого раздела используются модели формы. Тем не менее,
всё может быть в равной степени применено и к моделям [Active Record](/doc/guide/database.ar).

Определение класса модели
-------------------------

Ниже мы создадим класс модели `LoginForm`, который будет использоваться для
получения данных, вводимых пользователем на странице аутентификации. Поскольку
эти данные используются исключительно в целях аутентификации пользователя и
сохранять их не требуется, то создадим модель `LoginForm` как модель формы.

~~~
[php]
class LoginForm extends CFormModel
{
	public $username;
	public $password;
	public $rememberMe=false;
}
~~~

Мы объявили в `LoginForm` три атрибута: `$username`, `$password` и `$rememberMe`.
Они используются для хранения имени пользователя, пароля, а также значения опции сохранения
имени пользователя. Так как `$rememberMe` по умолчанию имеет значение `false`, то изначально
в форме аутентификации галочка этой опции будет снята.

> Info|Информация: Термин «атрибут» используется, чтобы отделить свойства, определяемые этими
переменными-членами класса, от прочих свойств. Здесь атрибут — это свойство, которое
используется в основном для хранения данных, вводимых пользователем, или данных,
получаемых из базы данных.

Определение правил проверки
--------------------------

В момент, когда пользователь отправляет данные формы, а модель их получает,
нам необходимо удостовериться, что эти данные корректны, прежде чем мы будем их использовать.
Это осуществляется посредством проверки данных в соответствии с набором правил.
Правила проверки задаются в методе `rules()`, который возвращает массив сконфигурированных правил.

~~~
[php]
class LoginForm extends CFormModel
{
	public $username;
	public $password;
	public $rememberMe=false;

	private $_identity;

	public function rules()
	{
		return array(
			array('username, password', 'required'),
			array('rememberMe', 'boolean'),
			array('password', 'authenticate'),
		);
	}

	public function authenticate($attribute,$params)
	{
		$this->_identity=new UserIdentity($this->username,$this->password);
		if(!$this->_identity->authenticate())
			$this->addError('password','Неправильное имя пользователя или пароль.');
	}
}
~~~

В коде, представленном выше, `username` и `password` — обязательные для заполнения поля,
поле `password` должно быть проверено также на соответствие указанному имени пользователя.
Поле `rememberMe` может принимать значения `true` или `false`.

Каждое правило, возвращаемое `rules()`, должно быть задано в следующем формате:

~~~
[php]
array('AttributeList', 'Validator', 'on'=>'ScenarioList', …дополнительные параметры)
~~~

где `AttributeList` — строка, содержащая разделённые запятыми имена атрибутов, которые должны
быть проверены в соответствии с правилами; `Validator` указывает на тип используемой проверки;
параметр `on` — необязательный параметр, определяющий список сценариев, в которых должно
использоваться правило; дополнительные параметры — это пары имя-значение, которые используются для
инициализации значений [свойств соответствующих валидаторов](/doc/guide/basics.component).

Начиная с версии 1.1.11 можно исключать отдельные правила. Если вы не хотите
проводить валидацию для какого-либо правила и сценария, можно указать параметр
`except` с указанием имени сценария. Синтаксис точно такой же, как и для
параметра `on`.

Список сценариев (в параметрах `on` и `except`) может быть указан двумя
эквивалентными способами:

~~~
[php]
// в виде массива имён сценариев
'on'=>array('update', 'create'),
// строкой с именами, разделённой запятыми (пробелы не учитываются)
'except'=>'ignore, this, scenarios, at-all',
~~~

Есть три способа указать `Validator` в правиле проверки. Во-первых, `Validator` может быть именем
метода в классе модели данных, аналогично `authenticate` в примере выше. Метод проверки
оформляется следующим образом:

~~~
[php]
/**
 * @param string $attribute имя поля, которое будем валидировать
 * @param array $params дополнительные параметры для правила валидации
 */
public function ValidatorName($attribute,$params) { … }
~~~

Второй способ — указать `Validator` в качестве имени класса. В этом случае для проверки данных в момент применения правила создаётся
экземпляр класса проверки. Дополнительные параметры в правиле используются для
инициализации значений его атрибутов. Класс проверки должен быть производным классом от [CValidator].

Третий вариант — предопределить псевдоним класса валидатора. В примере выше
`required` — это псевдоним класса [CRequiredValidator], который проверяет, не является ли значение атрибута пустым.
Ниже приведён полный список предопределенных псевдонимов валидаторов,
включенных в состав Yii:

   - `boolean`: псевдоним класса [CBooleanValidator], который проверяет, равно ли
значение атрибута [CBooleanValidator::trueValue] или [CBooleanValidator::falseValue];

   - `captcha`: псевдоним класса [CCaptchaValidator], который проверяет, равно ли
значение атрибута коду верификации на [капче](http://ru.wikipedia.org/wiki/Captcha);

   - `compare`: псевдоним класса [CCompareValidator], который проверяет, совпадает ли
значение атрибута со значением другого атрибута или константой;

   - `email`: псевдоним класса [CEmailValidator], который отвечает за проверку корректности email адреса;

   - `date`: псевдоним класса [CDateValidator], проверяющего, является ли атрибут
корректной датой, временем или и тем и другим.

   - `default`: псевдоним класса [CDefaultValueValidator], который присваивает значение
по умолчанию выбранным атрибутам;

   - `exist`: псевдоним класса [CExistValidator], который проверяет наличие значения атрибута в указанном столбце таблицы базы данных;

   - `file`: псевдоним класса [CFileValidator], отвечающего за проверку атрибута на
наличие в нём имени загруженного файла;

   - `filter`: псевдоним класса [CFilterValidator], преобразующего атрибут с использованием фильтра;

   - `in`: псевдоним класса [CRangeValidator], который проверяет, содержится ли значение атрибута в
указанном наборе значений;

   - `length`: псевдоним класса [CStringValidator], который проверяет, находится ли длина строкового значения атрибута в
указанном интервале;

   - `match`: псевдоним класса [CRegularExpressionValidator], проверяющего значение атрибута на соответствие регулярному выражению;

   - `numerical`: псевдоним класса [CNumberValidator], проверяющего, является ли значение атрибута числом;

   - `required`: псевдоним класса [CRequiredValidator], который проверяет, не является ли значение атрибута пустым;

   - `type`: псевдоним класса [CTypeValidator], проверяющего значение атрибута на соответствие указанному типу данных;

   - `unique`: псевдоним класса [CUniqueValidator], который проверяет, является ли значение атрибута уникальными в пределах столбца таблицы базы данных;

   - `url`: псевдоним класса [CUrlValidator], отвечающего за проверку корректности URL.

Ниже представлены несколько примеров использования предопределенных валидаторов:

~~~
[php]
// имя пользователя — обязательное поле формы
array('username', 'required'),
// длина имени пользователя должна быть от 3 до 12 символов включительно
array('username', 'length', 'min'=>3, 'max'=>12),
// в сценарии регистрации значения полей «password» и «password2» должны быть равны
array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'),
// в сценарии аутентификации поле `password` должно быть проверено на соответствие указанному имени пользователя
array('password', 'authenticate', 'on'=>'login'),
~~~


Безопасное присваивание значений атрибутам
------------------------------------------

После того как создан экземпляр модели данных, нам часто требуется заполнить его данными,
которые ввёл пользователь. Это очень легко сделать, используя массовое присваивание:

~~~
[php]
$model=new LoginForm;
if(isset($_POST['LoginForm']))
	$model->attributes=$_POST['LoginForm'];
~~~

Последнее выражение в примере как раз и является *массовым присваиванием*, где значение каждой переменной в
`$_POST['LoginForm']` присваивается соответствующему атрибуту модели.
Это эквивалентно следующей операции:

~~~
[php]
foreach($_POST['LoginForm'] as $name=>$value)
{
	if($name является безопасным атрибутом)
		$model->$name=$value;
}
~~~

Очень важно определить, какие атрибуты являются безопасными. Например, если мы
сделаем первичный ключ таблицы безопасным, злоумышленник получит шанс его записать и,
таким образом, изменить данные, которые он не должен менять, поскольку не обладает достаточными правами.

### Описание безопасных атрибутов

Атрибут считается безопасным, если он присутствует в правиле валидации,
применяемом в данном сценарии. Например,

~~~
[php]
array('username, password', 'required', 'on'=>'login, register'),
array('email', 'required', 'on'=>'register'),
~~~

В коде выше атрибуты `username` и `password` необходимы в сценарии
`login`, а атрибуты `username`, `password` и `email` — в
сценарии `register`. В результате, если мы проводим массовое присваивание в
сценарии `login`, то только атрибуты `username` и `password` будут массово
присвоены, т.к. только они входят в правило валидации для сценария `login`.
С другой стороны, если текущим сценарием является `register`, то все три атрибута могут
быть массово присвоены.

~~~
[php]
// сценарий входа
$model=new User('login');
if(isset($_POST['User']))
	$model->attributes=$_POST['User'];

// сценарий регистрации
$model=new User('register');
if(isset($_POST['User']))
	$model->attributes=$_POST['User'];
~~~

Так почему же мы используем именно такую политику для определения, является атрибут
безопасным или нет? Если атрибут уже есть в одном или
нескольких правилах валидации, зачем беспокоиться о чём-то ещё?

Важно помнить, что правила валидации используются для проверки введённых
пользователем данных, а не данных, которые мы генерируем в коде (например,
текущее время или автоматически сгенерированный первичный ключ). Поэтому
НИКОГДА НЕ ДОБАВЛЯЙТЕ правила валидации для атрибутов, не доступных для ввода конечным пользователем.

Иногда мы хотим объявить атрибут безопасным, даже если в действительности не
имеем правила для него. Пример — текст статьи, который может
принимать любое введённое пользователем значение. Для этого мы можем использовать
специальное правило `safe`:

~~~
[php]
array('content', 'safe')
~~~

Существует также правило `unsafe`, используемое для явного указания небезопасного атрибута:

~~~
[php]
array('permission', 'unsafe')
~~~

Правило `unsafe` используется редко и является противоположным описанному
нами ранее определению безопасных атрибутов.

Для установки значений небезопасных атрибутов мы должны использовать отдельные операции присваивания:

~~~
[php]
$model->permission='admin';
$model->id=1;
~~~


Выполнение проверки
---------------------

Как только модель будет заполнена пользовательскими данными, мы можем вызвать метод [CModel::validate()],
чтобы запустить процесс проверки. По итогам проверки метод возвращает положительный
или отрицательный результат. Для моделей [CActiveRecord] проверка может выполняться
автоматически в момент вызова метода [CActiveRecord::save()].

Мы можем задать сценарий через свойство [scenario|CModel::scenario], таким образом
указав, какой набор правил будет использован для проверки.

Проверка выполняется в зависимости от сценария. Свойство [scenario|CModel::scenario]
задаёт сценарий, в котором используется модель, и определяет, какой набор правил валидации
будет использован. К примеру, в сценарии `login` мы хотим проверить только поля модели
пользователя `username` и `password`. В сценарии `register` нам необходимо проверять
большее количество данных: `email`, `address` и т.д. Ниже показано, как провести
проверку для сценария `register`:

~~~
[php]
// создаём модель User и устанавливаем сценарий `register`. Выражение ниже эквивалентно следующему:
// $model=new User;
// $model->scenario='register';
$model=new User('register');

// наполняем модель данными
$model->attributes=$_POST['User'];

// выполняем проверку
if($model->validate())   // если данные верны
    …
else
    …
~~~

Сценарий для правил проверки задаётся в свойстве `on` правила. Если `on` не определено,
правило используется для всех сценариев. Например,

~~~
[php]
public function rules()
{
	return array(
		array('username, password', 'required'),
		array('password_repeat', 'required', 'on'=>'register'),
		array('password', 'compare', 'on'=>'register'),
	);
}
~~~

Первое правило будет распространяться на любые сценарии, а два последующих будут применяться
только к сценарию `register`.

Информация об ошибках
----------------------------

После проверки все возможные ошибки находятся в объекте модели. Мы можем получить
их через [CModel::getErrors()] и [CModel::getError()]. Первый метод возвращает
*все* ошибки для указанного атрибута модели, второй — *только первую* ошибку.

Чтобы узнать, возникли ли во время выполнения проверки какие-либо ошибки,
можно воспользоваться методом [CModel::hasErrors()]. И, если ошибки действительно есть,
получить их можно с помощью метода [CModel::getErrors()]. Оба метода могут быть
использованы как для всех, так и для конкретного атрибута.

Метки атрибутов
----------------

Часто при работе с формами для каждого поля требуется отображать его метку. Она
подсказывает пользователю, какие данные ему требуется ввести в поле. Мы, конечно, можем
задать метки полей в представлении, но, если указать их непосредственно в модели данных, мы
выиграем в удобстве и гибкости.

По умолчанию [CModel] в качестве меток возвращает названия атрибутов. Изменить их можно,
переопределив метод [attributeLabels()|CModel::attributeLabels].

Далее мы увидим, что возможность указания меток в модели данных позволяет быстро создавать сложные формы.